package net;

import java.lang.reflect.InvocationTargetException;
import java.nio.ByteOrder;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;

import system.Config;
import system.L;
import util.ChannelBufferUtil;
import data.ISerializable;

public class ServerSocketHandler extends SimpleChannelUpstreamHandler {
	
	public MessageDispatcher dispatcher;
    
//    private int _expectLength = Protocol.HEAD_LENGTH;
//    private boolean _readingHead = true;
    public Channel _channel;
    public ClientContext context = new ClientContext();
    
    @Override
    public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) {
    	L.debug(e.getChannel().getRemoteAddress()  + " close");
    }
    
    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    	L.debug(e.getChannel().getRemoteAddress()  + " connect");
    	if (Config.stopNet) {
    		ctx.getChannel().close();
    	}
    }
    
    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    	L.debug(e.getChannel().getRemoteAddress()  + " disconnect");
    }
    
    
    public ServerSocketHandler(MessageDispatcher dispatcher) {
    	this.dispatcher = dispatcher;
    	context.syner = this;
    }
    
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    	ChannelBuffer data = (ChannelBuffer)e.getMessage();
    	_channel = e.getChannel();
    	onData(data);
    }
    
	public void onData(ChannelBuffer data) throws Exception {
		context.version = data.readInt();
		String msg = ChannelBufferUtil.readString(data);
		context.mrid = data.readInt();
		if (!dispatcher.dispatch(context, msg, data)) {
			_channel.close();
		}
	}
	
	private int _headIndex;
	private ChannelBuffer _sending;
	
	public ChannelBuffer prepareMsg(String name, int size, int rid) {
		_sending = ChannelBuffers.dynamicBuffer(ByteOrder.LITTLE_ENDIAN, size);
		_headIndex = _sending.writerIndex();
		_sending.writeInt(0);
		_sending.writeInt(Config.VERSION);
		_sending.writeByte(Config.OPEN_VIP);
		ChannelBufferUtil.writeString(_sending, name);
		_sending.writeInt(rid);
		return _sending;
	}
	
	public ChannelBuffer prepareMsg(String name, int size) {
		return prepareMsg(name, size, context.mrid);
	}
	
	public ChannelBuffer prepareMsg(String name) {
		return prepareMsg(name, 1024, context.mrid);		
	}
	
	public ChannelBuffer prepareQueryResult() {
		return prepareMsg("QueryResult");
	}
		
    public void sendMsg(boolean close) {
    	int len = _sending.writerIndex() - _headIndex - Protocol.HEAD_LENGTH;
    	_sending.setInt(_headIndex, len);
    	ChannelFuture future = _channel.write(_sending);
		if (close) {
			future.addListener(ChannelFutureListener.CLOSE);
		}
    }
    
	public void appendInt(int i) {
		_sending.writeByte(Protocol.TYPE_INT);
		_sending.writeInt(i);
	}
	
	public void appendObject(ISerializable obj) {
		_sending.writeByte(Protocol.TYPE_OBJECT);
		ChannelBufferUtil.writeObject(_sending, obj);
	}

	public void appendString(String str) {
		_sending.writeByte(Protocol.TYPE_STRING);
		ChannelBufferUtil.writeString(_sending, str);
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
		L.error(e.toString());
		Throwable cause = e.getCause();
		StackTraceElement[] trace;
		if (cause instanceof OutOfMemoryError) {
			Config.SERVER_STATE = 1;
		}
		if (cause instanceof InvocationTargetException) {
			InvocationTargetException ite = (InvocationTargetException)cause;
			L.error(ite.getTargetException().toString());
			trace = ite.getTargetException().getStackTrace();
		} else {
			trace = cause.getStackTrace();
		}
		
		for (StackTraceElement st: trace) {
			L.error(st.toString());
		}
		this.close();
	}
	
	public void close() {
		_channel.close();
	}
}
